package com.gitee.oeoe.hot.refresh;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * mybatis Configuration 处理工具
 *
 * @author oeoe
 * @see Configuration
 * @since jdk1.8+ 2023/1/7
 */
@Slf4j
public class ConfigurationUtil {


    /**
     * 获取xml的namespace
     *
     * @param resource 资源文件
     * @return 命名空间名
     */
    public static String getNamespace(Resource resource) {
        try {
            XPathParser parser = new XPathParser(resource.getInputStream(), false, null, new XMLMapperEntityResolver());
            return parser.evalNode("/mapper").getStringAttribute("namespace");
        } catch (Exception e) {
            throw new RuntimeException("ERROR: 解析xml中namespace失败", e);
        }
    }


    /**
     * 删除xml元素的节点缓存
     *
     * @param nameSpace xml中命名空间
     */
    public static void clearMap(Configuration configuration, String nameSpace) {
        Arrays.asList(
                "mappedStatements"
                , "caches"
                , "resultMaps"
                , "parameterMaps"
                , "keyGenerators"
                , "sqlFragments"
        ).forEach(fieldName -> {
            Object value = getFieldValue(configuration, fieldName);
            if (value instanceof Map) {
                Map<?, ?> map = (Map<?, ?>) value;
                List<?> list = map.keySet().stream().filter(o -> o.toString().startsWith(nameSpace + ".")).collect(Collectors.toList());
                list.forEach(map::remove);
            }
        });
    }


    /**
     * 清除文件记录缓存
     *
     * @param resource xml文件路径
     */
    public static void clearSet(Configuration configuration, Resource resource) {
        Object value = getFieldValue(configuration, "loadedResources");
        if (value instanceof Set) {
            Set<?> set = (Set<?>) value;
            set.remove(resource);
            set.remove("namespace:" + resource);
        }
    }

    /**
     * 收集文件
     *
     * @param dir 当前目录
     * @return 文件名和文件
     */
    @SneakyThrows
    public static Set<Path> findFile(Path dir, Predicate<Path> filter) {

        Set<Path> files = new HashSet<>();

        Files.walkFileTree(dir, new FileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (filter.test(file)) {
                    files.add(file);
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                if (filter.test(dir)) {
                    files.add(dir);
                }
                return FileVisitResult.CONTINUE;
            }
        });
        return files;
    }


    /**
     * 获取对象指定属性
     *
     * @param obj       对象信息
     * @param fieldName 属性名称
     * @return 字段值, 若没有获取到, 返回null
     */
    public static Object getFieldValue(Object obj, String fieldName) {
        Class<?> cls = obj.getClass();
        do {
            try {
                Field field = cls.getDeclaredField(fieldName);
                boolean accessible = field.isAccessible();
                field.setAccessible(true);
                Object value = field.get(obj);
                field.setAccessible(accessible);
                return value;
            } catch (Exception ignored) {
            }
            cls = cls.getSuperclass();
        } while (cls != Object.class);
        log.error(String.format("获取属性[%s]失败", fieldName));
        return null;
    }
}
